1 /** 2 User-defined attributes that can be used with the KeyedItem mixin. 3 The constraints module contains: 4 $(TOC UniqueConstraintColumn) 5 $(TOC PrimaryKeyColumn) 6 $(TOC ExclusionConstraint) 7 $(TOC CheckConstraint) 8 $(TOC NotNull) 9 $(TOC SetConstraint) 10 $(TOC EnumConstraint) 11 $(TOC Rule) 12 $(TOC ForeignKey) 13 $(TOC ForeignKeyConstraint) 14 $(TOC Default) 15 16 License: $(GPL2) 17 18 Authors: Matthew Armbruster 19 20 $(B Source:) $(SRC $(SRCFILENAME)) 21 Copyright: 2016 22 */ 23 module db_constraints.constraints; 24 25 import std.functional : binaryFun, unaryFun; 26 27 /** 28 KeyedItem will create a struct with $(I name) defined in the compile-time 29 argument. For example a property marked with @UniqueColumn!("uc_Person") will 30 be part of the struct uc_Person. 31 Params: 32 name_ = The name of the constraint which is the structs name 33 */ 34 struct UniqueConstraintColumn(string name_) 35 { 36 enum name = name_; 37 } 38 39 /** 40 An alias for the primary key column. A member with this attribute 41 must also have the NotNull attribute. 42 */ 43 alias PrimaryKeyColumn = UniqueConstraintColumn!("PrimaryKey"); 44 45 /** 46 Mimics Postgresql's Exclude Constraint. This will exclude any 47 items that return true to the $(D exclusion_). 48 49 Version: \>= 0.0.7 50 */ 51 struct ExclusionConstraint(alias exclusion_, string name_ = "") 52 if (is(typeof(binaryFun!exclusion_))) 53 { 54 alias exclusion = binaryFun!exclusion_; 55 enum name = name_; 56 } 57 58 /** 59 $(WIKI keyeditem, KeyedItem.checkConstraints) will check all of the members 60 marked with this attribute and use the check given. 61 62 Version: \>= 0.0.6 allows you to mark your class as well. 63 64 Params: 65 check_ = The function that returns a boolean 66 name_ = Name used in the error message if the function returns false 67 */ 68 struct CheckConstraint(alias check_, string name_ = "") 69 if (is(typeof(unaryFun!check_))) 70 { 71 alias check = unaryFun!check_; 72 enum name = name_; 73 } 74 75 /** 76 Alias for a special check constraint that makes sure the column is never null. 77 This is checked the same time as all the other check constraints. The name of 78 the constraint is NotNull in the error messages if this is ever violated. 79 */ 80 alias NotNull = CheckConstraint!( 81 function bool(auto ref a) 82 { 83 static if (__traits(hasMember, typeof(a), "isNull")) 84 { 85 return !a.isNull; 86 } 87 else static if (__traits(compiles, typeof(a).init == null)) 88 { 89 return a !is null; 90 } 91 else 92 { 93 return true; 94 } 95 }, "NotNull"); 96 97 import std.traits : isExpressions; 98 /** 99 Alias for check constraint that makes sure the property that 100 has this attribute only contains members in the set. This should 101 act like the SET constraint in MySQL. It will sort and remove the 102 duplicates of the SET. This does modify the value coming in. This 103 is only for strings. 104 105 If $(D isStrict) is true, SetConstraint will return false if 106 you include a value not in the set. If $(D isStrict) is 107 false, the value will be set to an empty string. 108 109 Version: \>= 0.0.6 for $(D isStrict) option. 110 \>= 0.0.4 is always strict. 111 */ 112 template SetConstraint(values...) 113 if (isExpressions!values) 114 { 115 alias SetConstraint = SetConstraint!(true, values); 116 } 117 /// ditto 118 template SetConstraint(bool isStrict, values...) 119 if (isExpressions!values) 120 { 121 alias SetConstraint = CheckConstraint!( 122 function bool(auto ref a) 123 { 124 static assert(is(typeof(a) == string)); 125 126 if (a !is null) 127 { 128 import std.array : split; 129 import std.algorithm : among, aSort = sort, uniq; 130 auto options = a.split(",").aSort.uniq; 131 a = ""; 132 foreach(string option; options) 133 { 134 if (!option.among!(values)) 135 { 136 static if (isStrict) 137 { 138 return false; 139 } 140 else 141 { 142 continue; 143 } 144 } 145 if (a != "") 146 { 147 a ~= ","; 148 } 149 a ~= option; 150 } 151 } 152 return true; 153 }, "Set"); 154 } 155 156 /** 157 Alias for check constraint that makes sure the property that 158 has this attribute only contains a member that is part of the 159 enumeration. This should act like the ENUM constraint in MySQL. 160 This does modify the value coming in. This is only for strings. 161 162 If $(D isStrict) is true, EnumConstraint will return false if 163 you include a value not in the enumeration. If $(D isStrict) is 164 false, the value will be set to an empty string. 165 166 Version: \>= 0.0.6 167 */ 168 template EnumConstraint(values...) 169 if (isExpressions!values) 170 { 171 alias EnumConstraint = EnumConstraint!(true, values); 172 } 173 /// ditto 174 template EnumConstraint(bool isStrict, values...) 175 if (isExpressions!values) 176 { 177 alias EnumConstraint = CheckConstraint!( 178 function bool(auto ref a) 179 { 180 import std.algorithm : among; 181 static assert(is(typeof(a) == string)); 182 183 if (a !is null) 184 { 185 static if (isStrict) 186 { 187 return a.among!(values); 188 } 189 else 190 { 191 if (!a.among!(values)) 192 { 193 a = ""; 194 } 195 } 196 } 197 return true; 198 }, "Enum"); 199 } 200 201 /** 202 Rules for foreign keys when updating or deleting. 203 */ 204 enum Rule 205 { 206 /** 207 When a parent key is modified or deleted from the collection, no special 208 action is taken. If you are using MySQL or MSSQL use 209 $(SRCTAG Rule.restrict) instead for the desired effect. 210 */ 211 noAction, 212 /** 213 The item is prohibited from deleting or modifying a parent key when there exists 214 one or more child keys mapped to it. This is the default. 215 216 $(THROWS ForeignKeyException, when a member changes.) 217 */ 218 restrict, 219 /** 220 Sets the member to $(D null) when deleting or modifying a parent key. 221 222 $(THROWS ForeignKeyException, when the type cannot be set to null.) 223 */ 224 setNull, 225 /** 226 Sets the member to the Default value when deleting or modifying a parent key. 227 If there is no defined Default then the member is set to its types initial 228 value. 229 */ 230 setDefault, 231 /** 232 Updates or deletes the item based on what happened to the parent key. 233 */ 234 cascade 235 } 236 237 /** 238 $(SRCTAG ForeignKeyConstraint) should be used instead of this struct. 239 This is more the behind the scenes struct. 240 Params: 241 name_ = The name of the foreign key constraint. Will be used in error message when violated 242 columnNames_ = The members in the child class that are used in the foreign key 243 referencedTableName_ = The referenced table's name (collection class) 244 referencedColumnNames_ = The members in the parent class that are references in the foreign key 245 updateRule_ = Rule when a foreign key is updated that is being referenced 246 deleteRule_ = Rule when a foreign key is deleted that is being referenced 247 */ 248 struct ForeignKey(string name_, 249 string[] columnNames_, 250 string referencedTableName_, 251 string[] referencedColumnNames_, 252 Rule updateRule_, 253 Rule deleteRule_) 254 { 255 enum string name = name_; 256 enum string[] columnNames = columnNames_; 257 enum string referencedTableName = referencedTableName_; 258 enum string[] referencedColumnNames = referencedColumnNames_; 259 enum Rule updateRule = updateRule_; 260 enum Rule deleteRule = deleteRule_; 261 } 262 263 /** 264 The foreign key user-defined attribute. 265 */ 266 template ForeignKeyConstraint(string name_, string[] columnNames_, 267 string referencedTableName_, 268 string[] referencedColumnNames_, 269 Rule updateRule_, Rule deleteRule_) 270 { 271 alias ForeignKeyConstraint = ForeignKey!(name_, columnNames_, 272 referencedTableName_, 273 referencedColumnNames_, 274 updateRule_, deleteRule_); 275 } 276 277 /// ditto 278 template ForeignKeyConstraint(string name_, string[] columnNames_, 279 string referencedTableName_, 280 string[] referencedColumnNames_) 281 { 282 alias ForeignKeyConstraint = ForeignKey!(name_, columnNames_, 283 referencedTableName_, 284 referencedColumnNames_, 285 Rule.restrict, Rule.restrict); 286 } 287 288 /// ditto 289 template ForeignKeyConstraint(string[] columnNames_, 290 string referencedTableName_, 291 string[] referencedColumnNames_, 292 Rule updateRule_, Rule deleteRule_) 293 { 294 alias ForeignKeyConstraint = ForeignKey!("", columnNames_, 295 referencedTableName_, 296 referencedColumnNames_, 297 updateRule_, deleteRule_); 298 } 299 300 /// ditto 301 template ForeignKeyConstraint(string[] columnNames_, 302 string referencedTableName_, 303 string[] referencedColumnNames_) 304 { 305 alias ForeignKeyConstraint = ForeignKey!("", columnNames_, 306 referencedTableName_, 307 referencedColumnNames_, 308 Rule.restrict, Rule.restrict); 309 } 310 311 /** 312 Default used with $(SRCTAG Rule.setDefault) for foreign keys. 313 Params: 314 value_ = the value that should be used for the default value. 315 */ 316 struct Default(alias value_) 317 { 318 enum value = value_; 319 } 320 321